
#include "orangepch.h"
#include "Renderer2D.h"
#include "VertexArray.h"
#include "Platform/OpenGl/OpenGLShader.h"
#include "RendererCommand.h"
#include "glm/gtc/matrix_transform.hpp"
namespace Orange {

	struct QuadVertex {
		glm::vec3 Position;
		glm::vec4 Color;
		glm::vec2 TexCoord;
		float TexIndex;
		float TileFactory;
		
	};

	struct Renderer2DData {

		const uint32_t MaxQuadCount = 10;
		const uint32_t MaxVertex = MaxQuadCount * 4;
		const uint32_t MaxIndexSize = MaxQuadCount * 6;
		static const uint32_t MaxTextureSlots = 32;
		

		Ref<VertexBuffer> QuadVB;

		Ref<VertexArray> m_VertexArray;
		Ref<Shader> m_Shader;
		Ref<Texture2D> m_WhiteTexture;

		QuadVertex* quadVertexBufferBase = nullptr;
		QuadVertex* quadVertexBufferPtr = nullptr;

		uint32_t indexesCount = 0;

		std::array<Ref<Texture>, MaxTextureSlots> textureSlots;
		uint32_t textureSlotIndex = 1; // 0 is WhiteTexture

		glm::vec4 vertexPosition[4];

		Renderer2D::stats stat;
	};
	static Renderer2DData s_Data;
	void Renderer2D::Init()
	{


		s_Data.m_VertexArray = (Orange::VertexArray::Create());

		s_Data.QuadVB.reset(Orange::VertexBuffer::Create(s_Data.MaxVertex * sizeof(QuadVertex)));


		Orange::BufferLayout squareLayout = {
			{ Orange::ShaderDataType::Float3,"a_Position"},
			{ Orange::ShaderDataType::Float4,"a_Color"},
			{ Orange::ShaderDataType::Float2,"a_TexCrood"},
			{ Orange::ShaderDataType::Float,"a_TexIndex"},
			{ Orange::ShaderDataType::Float,"a_TileFactory"}
		};


		s_Data.QuadVB->setLayout(squareLayout);

		s_Data.m_VertexArray->AddVertexBuffer(s_Data.QuadVB);

		s_Data.quadVertexBufferBase = new QuadVertex[s_Data.MaxVertex];

		uint32_t* QuadIBs = new uint32_t[s_Data.MaxIndexSize];
		uint32_t offset = 0;
		for (uint32_t i = 0; i < s_Data.MaxIndexSize; i+=6)
		{
			QuadIBs[i + 0] = offset + 0;
			QuadIBs[i + 1] = offset + 1;
			QuadIBs[i + 2] = offset + 2;

			QuadIBs[i + 3] = offset + 0;
			QuadIBs[i + 4] = offset + 2;
			QuadIBs[i + 5] = offset + 3;

			offset += 4;
		}
		Ref<IndexBuffer> QuadIB;
			QuadIB.reset(Orange::IndexBuffer::Create(QuadIBs, s_Data.MaxIndexSize));
	

		s_Data.m_VertexArray->SetIndexBuffer(QuadIB);

		delete[] QuadIBs;


		s_Data.m_WhiteTexture = Texture2D::Create(1, 1);
		uint32_t WhiteTextureData = 0xffffffff;
		s_Data.m_WhiteTexture->SetData(&WhiteTextureData, sizeof(uint32_t));

		s_Data.m_Shader = Orange::Shader::Create("assets/shader/texture2D.glsl");

		int32_t samplers[s_Data.MaxTextureSlots] ;
		for (uint32_t i = 0; i < s_Data.MaxTextureSlots; i++)
		{
			samplers[i] = i;
		}

		s_Data.m_Shader->bind();
		s_Data.m_Shader->SetIntArray("u_Textures", samplers, s_Data.MaxTextureSlots);

		// init textureSlots
		s_Data.textureSlots[0] = s_Data.m_WhiteTexture;

		s_Data.vertexPosition[0] = glm::vec4({ -0.5f, -0.5f, 0.0f , 1.0f }); // ����
		s_Data.vertexPosition[1] = glm::vec4({  0.5f, -0.5f, 0.0f , 1.0f }); // ����
		s_Data.vertexPosition[2] = glm::vec4({  0.5f,  0.5f, 0.0f , 1.0f }); // ����
		s_Data.vertexPosition[3] = glm::vec4({ -0.5f,  0.5f, 0.0f , 1.0f }); // ����

	}

	void Renderer2D::StartScene(const glm::mat4& prejectMatrix, const glm::mat4& transform)
	{
		s_Data.m_Shader->bind();
		glm::mat4 viewPrejectMatrix = transform * prejectMatrix;
		s_Data.m_Shader->SetMat4("v_viewPreject", viewPrejectMatrix);

		s_Data.indexesCount = 0;
		s_Data.textureSlotIndex = 1; //
		s_Data.quadVertexBufferPtr = s_Data.quadVertexBufferBase;
	}

	void Renderer2D::StartScene(OrthogonalCamera& camera)
	{
		s_Data.m_Shader->bind();
		s_Data.m_Shader->SetMat4("v_viewPreject", camera.GetViewPrejectMatrix());

		s_Data.indexesCount = 0;
		s_Data.textureSlotIndex = 1; //
		s_Data.quadVertexBufferPtr = s_Data.quadVertexBufferBase;

	}

	void Renderer2D::EndScene()
	{	
		
		uint64_t dataSize = (uint64_t)s_Data.quadVertexBufferPtr - (uint64_t)s_Data.quadVertexBufferBase;
		s_Data.QuadVB->SetData(s_Data.quadVertexBufferBase, dataSize);
		Flush();
	}

	void Renderer2D::Flush()
	{
		for (uint32_t i = 0; i < s_Data.textureSlotIndex; i++)
		{
			s_Data.textureSlots[i]->Bind(i);
		}

		RendererCommand::DrawIndexs(s_Data.m_VertexArray , s_Data.indexesCount);

		s_Data.stat.DrawCall++;
	}

	void Renderer2D::Shutdown()
	{
		
	}

	void Renderer2D::FlushAndReset() {
		EndScene();

		s_Data.indexesCount = 0;
		s_Data.textureSlotIndex = 1; //
		s_Data.quadVertexBufferPtr = s_Data.quadVertexBufferBase;

	}

	void Renderer2D::DrawQuad(const glm::vec2& position, const glm::vec2& size, const glm::vec4& color)
	{
		DrawQuad({ position.x,position.y, 0.0f }, size, color);
	}

	void Renderer2D::DrawQuad(const glm::vec3& position, const glm::vec2& size, const glm::vec4& color)
	{

		glm::mat4 transform = glm::translate(glm::mat4(1.0f), position)
			* glm::scale(glm::mat4(1.0f), glm::vec3(size, 1.0f));

		DrawQuad(transform, color);
	
	}

	void Renderer2D::DrawQuad(const glm::mat4& transform, const glm::vec4& color)
	{
		if (s_Data.indexesCount >= s_Data.MaxIndexSize) {
			FlushAndReset();
		}
		float texIndex = 0.0f;
		float tileFactory = 0.0f;
	

		glm::vec2 TexCoord[] = {
		 { 0.0f , 0.0f },
		{ 1.0f, 0.0f },
		{ 1.0f, 1.0f },
		{ 0.0f, 1.0f }
		};

		for (uint32_t i = 0; i < 4; i++)
		{
			s_Data.quadVertexBufferPtr->Position = transform * s_Data.vertexPosition[i];  // ����
			s_Data.quadVertexBufferPtr->Color = color;
			s_Data.quadVertexBufferPtr->TexCoord = TexCoord[i];
			s_Data.quadVertexBufferPtr->TexIndex = texIndex;
			s_Data.quadVertexBufferPtr->TileFactory = tileFactory;
			s_Data.quadVertexBufferPtr++;

		}






		s_Data.indexesCount += 6;

		s_Data.stat.QuadCount++;

	}



	void Renderer2D::DrawQuad(const glm::vec2& position, const glm::vec2& size, const Ref<Texture2D>& texture, const float u_TileFactory, const glm::vec4& tinyColor)
	{
		DrawQuad({ position.x,position.y, 0.0f }, size, texture, u_TileFactory, tinyColor);
	}

	void Renderer2D::DrawQuad(const glm::vec3& position, const glm::vec2& size, const Ref<Texture2D>& texture, const float u_TileFactory, const glm::vec4& tinyColor)
	{
		glm::mat4 transform = glm::translate(glm::mat4(1.0f), position)
			* glm::scale(glm::mat4(1.0f), glm::vec3(size, 1.0f));
		
		DrawQuad(transform, texture, u_TileFactory, tinyColor);

	}
	void Renderer2D::DrawQuad(const glm::mat4& transform, const Ref<Texture2D>& texture, const float u_TileFactory, const glm::vec4& tinyColor)
	{
		if (s_Data.indexesCount >= s_Data.MaxIndexSize) {
			FlushAndReset();
		}


		float textureIndex = 0.0f;
		// if texure is exist
		for (uint32_t i = 1; i < s_Data.textureSlotIndex; i++)
		{
			if (*texture.get() == *s_Data.textureSlots[i].get()) {
				textureIndex = (float)i;
				break;
			}
		}

		// if texure isn't exist 
		if (textureIndex == 0.0f) {
			textureIndex = (float)s_Data.textureSlotIndex;
			s_Data.textureSlots[s_Data.textureSlotIndex] = texture;
			s_Data.textureSlotIndex++;
		}

		constexpr glm::vec4 color = { 1.0f,1.0f, 1.0f, 1.0f };


		glm::vec2 TexCoord[] = {
				 { 0.0f , 0.0f },
				{ 1.0f, 0.0f },
				{ 1.0f, 1.0f },
				{ 0.0f, 1.0f }
		};

		for (uint32_t i = 0; i < 4; i++)
		{
			s_Data.quadVertexBufferPtr->Position = transform * s_Data.vertexPosition[i];  // ����
			s_Data.quadVertexBufferPtr->Color = color;
			s_Data.quadVertexBufferPtr->TexCoord = TexCoord[i];
			s_Data.quadVertexBufferPtr->TexIndex = textureIndex;
			s_Data.quadVertexBufferPtr->TileFactory = u_TileFactory;
			s_Data.quadVertexBufferPtr++;

		}






		s_Data.indexesCount += 6;

		s_Data.stat.QuadCount++;

	}


	void Renderer2D::DrawQuad(const glm::vec2& position, const glm::vec2& size, const Ref<SubTexture2D>& subTexture,  const float u_TileFactory, const glm::vec4& tinyColor)
	{
		DrawQuad({ position.x , position.y, 0.0f }, size, subTexture,  u_TileFactory, tinyColor);
	}

	void Renderer2D::DrawQuad(const glm::vec3& position, const glm::vec2& size, const Ref<SubTexture2D>& subTexture,  const float u_TileFactory, const glm::vec4& tinyColor)
	{
		if (s_Data.indexesCount >= s_Data.MaxIndexSize) {
			FlushAndReset();
		}

		 Ref<Texture> texture = subTexture->GetTexture();

		float textureIndex = 0.0f;
		// if texure is exist
		for (uint32_t i = 1; i < s_Data.textureSlotIndex; i++)
		{
			if (*texture.get() == *s_Data.textureSlots[i].get()) {
				textureIndex = (float)i;
				break;
			}
		}

		// if texure isn't exist 
		if (textureIndex == 0.0f) {
			textureIndex = (float)s_Data.textureSlotIndex;
			s_Data.textureSlots[s_Data.textureSlotIndex] = texture;
			s_Data.textureSlotIndex++;
		}

		constexpr glm::vec4 color = { 1.0f,1.0f, 1.0f, 1.0f };
		glm::mat4 transform = glm::translate(glm::mat4(1.0f), position)
			* glm::scale(glm::mat4(1.0f), glm::vec3(size, 1.0f));

		for (uint32_t i = 0; i < 4; i++)
		{
			s_Data.quadVertexBufferPtr->Position = transform * s_Data.vertexPosition[i];  // ����
			s_Data.quadVertexBufferPtr->Color = color;
			s_Data.quadVertexBufferPtr->TexCoord = subTexture->GetTexCoord()[i];
			s_Data.quadVertexBufferPtr->TexIndex = textureIndex;
			s_Data.quadVertexBufferPtr->TileFactory = u_TileFactory;
			s_Data.quadVertexBufferPtr++;

		}



		s_Data.indexesCount += 6;

		s_Data.stat.QuadCount++;
	}

	void Renderer2D::DrawRotationQuad(const glm::vec2& position, const glm::vec2& size, float rotation, const glm::vec4& color)
	{
		DrawRotationQuad({ position.x,position.y, 0.0f }, size, rotation, color);
	}

	void Renderer2D::DrawRotationQuad(const glm::vec3& position, const glm::vec2& size, float rotation, const glm::vec4& color)
	{

		if (s_Data.indexesCount >= s_Data.MaxIndexSize) {
			FlushAndReset();
		}
		float texIndex = 0.0f;
		float tileFactory = 0.0f;

		glm::mat4 transform = glm::translate(glm::mat4(1.0f), position)
			* glm::rotate(glm::mat4(1.0f), glm::radians(rotation), glm::vec3(0.0f, 0.0f, 1.0f))
			* glm::scale(glm::mat4(1.0f), glm::vec3(size, 1.0f));

		glm::vec2 TexCoord[] = {
				 { 0.0f , 0.0f },
				{ 1.0f, 0.0f },
				{ 1.0f, 1.0f },
				{ 0.0f, 1.0f }
		};

		for (uint32_t i = 0; i < 4; i++)
		{
			s_Data.quadVertexBufferPtr->Position = transform * s_Data.vertexPosition[i];  // ����
			s_Data.quadVertexBufferPtr->Color = color;
			s_Data.quadVertexBufferPtr->TexCoord = TexCoord[i];
			s_Data.quadVertexBufferPtr->TexIndex = texIndex;
			s_Data.quadVertexBufferPtr->TileFactory = tileFactory;
			s_Data.quadVertexBufferPtr++;

		}





		s_Data.indexesCount += 6;

		s_Data.stat.QuadCount++;
	}

	void Renderer2D::DrawRotationQuad(const glm::vec2& position, const glm::vec2& size, float rotation, const Ref<Texture2D>& texture, const float u_TileFactory, const glm::vec4& tinyColor)
	{
		DrawRotationQuad({ position.x,position.y, 0.0f }, size, rotation, texture, u_TileFactory, tinyColor);
	}

	void Renderer2D::DrawRotationQuad(const glm::vec3& position, const glm::vec2& size, float rotation, const Ref<Texture2D>& texture, const float u_TileFactory, const glm::vec4& tinyColor)
	{

		if (s_Data.indexesCount >= s_Data.MaxIndexSize) {
			FlushAndReset();
		}
		float textureIndex = 0.0f;
		// if texure is exist
		for (uint32_t i = 1; i < s_Data.textureSlotIndex; i++)
		{
			if (*s_Data.textureSlots[i].get()  == *texture.get()) {
				textureIndex = (float)i;
				break;
			}
		}

		// if texure isn't exist 
		if (textureIndex == 0.0f) {
			textureIndex = (float)s_Data.textureSlotIndex;
			s_Data.textureSlots[s_Data.textureSlotIndex] = texture;
			s_Data.textureSlotIndex++;
		}

		constexpr glm::vec4 color = { 1.0f,1.0f, 1.0f, 1.0f };

		glm::mat4 transform =glm::translate(glm::mat4(1.0f),position)
			* glm::rotate(glm::mat4(1.0f), glm::radians(rotation), glm::vec3(0.0f, 0.0f, 1.0f))
			* glm::scale(glm::mat4(1.0f), glm::vec3(size, 1.0f));

		glm::vec2 TexCoord[] = {
			 { 0.0f , 0.0f },
			{ 1.0f, 0.0f },
			{ 1.0f, 1.0f },
			{ 0.0f, 1.0f }
		};

		for (uint32_t i = 0; i < 4; i++)
		{
			s_Data.quadVertexBufferPtr->Position = transform * s_Data.vertexPosition[i];  // ����
			s_Data.quadVertexBufferPtr->Color = color;
			s_Data.quadVertexBufferPtr->TexCoord = TexCoord[i];
			s_Data.quadVertexBufferPtr->TexIndex = textureIndex;
			s_Data.quadVertexBufferPtr->TileFactory = u_TileFactory;
			s_Data.quadVertexBufferPtr++;

		}



		s_Data.indexesCount += 6;

		s_Data.stat.QuadCount++;
	}

	void Renderer2D::DrawRotationQuad(const glm::vec2& position, const glm::vec2& size, float rotation, const Ref<SubTexture2D>& subTexture,  const float u_TileFactory, const glm::vec4& tinyColor)
	{
		DrawRotationQuad({ position.x , position.y, 0.0f }, size, rotation , subTexture,  u_TileFactory, tinyColor);
	}

	void Renderer2D::DrawRotationQuad(const glm::vec3& position, const glm::vec2& size, float rotation, const Ref<SubTexture2D>& subTexture,  const float u_TileFactory, const glm::vec4& tinyColor)
	{
		if (s_Data.indexesCount >= s_Data.MaxIndexSize) {
			FlushAndReset();
		}
		const Ref<Texture> texture = subTexture->GetTexture();

		float textureIndex = 0.0f;
		// if texure is exist
		for (uint32_t i = 1; i < s_Data.textureSlotIndex; i++)
		{
			if (*s_Data.textureSlots[i].get() == *texture.get()) {
				textureIndex = (float)i;
				break;
			}
		}

		// if texure isn't exist 
		if (textureIndex == 0.0f) {
			textureIndex = (float)s_Data.textureSlotIndex;
			s_Data.textureSlots[s_Data.textureSlotIndex] = texture;
			s_Data.textureSlotIndex++;
		}

		constexpr glm::vec4 color = { 1.0f,1.0f, 1.0f, 1.0f };

		glm::mat4 transform = glm::translate(glm::mat4(1.0f), position)
			* glm::rotate(glm::mat4(1.0f), glm::radians(rotation), glm::vec3(0.0f, 0.0f, 1.0f))
			* glm::scale(glm::mat4(1.0f), glm::vec3(size, 1.0f));


		for (uint32_t i = 0; i < 4; i++)
		{
			s_Data.quadVertexBufferPtr->Position = transform * s_Data.vertexPosition[i];  // ����
			s_Data.quadVertexBufferPtr->Color = color;
			s_Data.quadVertexBufferPtr->TexCoord = subTexture->GetTexCoord()[i];
			s_Data.quadVertexBufferPtr->TexIndex = textureIndex;
			s_Data.quadVertexBufferPtr->TileFactory = u_TileFactory;
			s_Data.quadVertexBufferPtr++;

		}

		s_Data.indexesCount += 6;

		s_Data.stat.QuadCount++;
	}

	Renderer2D::stats Renderer2D::GetStat()
	{
		return s_Data.stat;
	}

	void Renderer2D::ResetStat()
	{
		memset(&s_Data.stat, 0, sizeof(Renderer2D::stats));
	}



}